/*
 * Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include "azure_c_shared_utility/optimize_size.h"
#include "azure_c_shared_utility/optionhandler.h"
#include "azure_c_shared_utility/tcpsocketconnection_c.h"
#include "azure_c_shared_utility/xio.h"
extern "C" {
#include "azure_c_shared_utility/optimize_size.h"
#include "azure_c_shared_utility/singlylinkedlist.h"
#include "azure_c_shared_utility/socketio.h"
#include "azure_c_shared_utility/xlogging.h"
}

#include "fff.h"
#include "gtest/gtest.h"

DEFINE_FFF_GLOBALS

class TestSocketioOpenIot : public ::testing::Test {
public:
    SOCKETIO_CONFIG io_create_parameters = {.hostname = "test", .port = 55};
    CONCRETE_IO_HANDLE socket_io = nullptr;
    ON_IO_OPEN_COMPLETE on_io_open_complete = {0};
    void *on_io_open_complete_context = {0};
    ON_BYTES_RECEIVED on_bytes_received = {0};
    void *on_bytes_received_context = {0};
    ON_IO_ERROR on_io_error = 0;
    void *on_io_error_context = {0};

    TestSocketioOpenIot()
    {
        RESET_FAKE(LogError);
        RESET_FAKE(singlylinkedlist_create);
        RESET_FAKE(singlylinkedlist_add);
        RESET_FAKE(tcpsocketconnection_create);
        RESET_FAKE(tcpsocketconnection_connect);
    }
};

TEST_F(TestSocketioOpenIot, creating_socketio_fails_when_no_parameters_are_passed)
{
    EXPECT_EQ(nullptr, socketio_create(nullptr));
}

TEST_F(TestSocketioOpenIot, creating_socketio_fails_when_linked_list_cannot_be_created)
{
    singlylinkedlist_create_fake.return_val = nullptr;

    EXPECT_EQ(nullptr, socketio_create(&io_create_parameters));
}

TEST_F(TestSocketioOpenIot, creating_socketio_succeeds_with_hostname_and_port_set)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;

    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    EXPECT_NE(nullptr, this->socket_io);

    socketio_destroy(this->socket_io);
}

TEST_F(TestSocketioOpenIot, opening_socketio_connection_fails_when_no_io_handle_passed)
{
    EXPECT_EQ(MU_FAILURE,
              socketio_open(nullptr,
                            this->on_io_open_complete,
                            this->on_io_open_complete_context,
                            this->on_bytes_received,
                            this->on_bytes_received_context,
                            this->on_io_error,
                            this->on_io_error_context));
}

TEST_F(TestSocketioOpenIot, opening_socketio_connection_fails_if_tcp_connection_cannot_be_created)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;
    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    tcpsocketconnection_create_fake.return_val = nullptr;

    EXPECT_EQ(MU_FAILURE,
              socketio_open(this->socket_io,
                            this->on_io_open_complete,
                            this->on_io_open_complete_context,
                            this->on_bytes_received,
                            this->on_bytes_received_context,
                            this->on_io_error,
                            this->on_io_error_context));

    socketio_destroy(this->socket_io);
}

TEST_F(TestSocketioOpenIot, opening_socketio_connection_fails_if_tcp_connection_cannot_connect)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;
    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    TCPSOCKETCONNECTION_HANDLE *socket_handle = (TCPSOCKETCONNECTION_HANDLE *)0xDEADBEEF;

    tcpsocketconnection_create_fake.return_val = socket_handle;
    tcpsocketconnection_connect_fake.return_val = MU_FAILURE;

    EXPECT_EQ(MU_FAILURE,
              socketio_open(this->socket_io,
                            this->on_io_open_complete,
                            this->on_io_open_complete_context,
                            this->on_bytes_received,
                            this->on_bytes_received_context,
                            this->on_io_error,
                            this->on_io_error_context));

    socketio_destroy(this->socket_io);
}

TEST_F(TestSocketioOpenIot, opening_socketio_fails_when_socket_already_opened)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;
    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    tcpsocketconnection_create_fake.return_val = (TCPSOCKETCONNECTION_HANDLE *)0xDEADBEEF;
    tcpsocketconnection_connect_fake.return_val = 0;
    EXPECT_EQ(0,
              socketio_open(this->socket_io,
                            this->on_io_open_complete,
                            this->on_io_open_complete_context,
                            this->on_bytes_received,
                            this->on_bytes_received_context,
                            this->on_io_error,
                            this->on_io_error_context));

    EXPECT_EQ(MU_FAILURE,
              socketio_open(this->socket_io,
                            this->on_io_open_complete,
                            this->on_io_open_complete_context,
                            this->on_bytes_received,
                            this->on_bytes_received_context,
                            this->on_io_error,
                            this->on_io_error_context));

    socketio_destroy(this->socket_io);
}

void opening_io(void *context, IO_OPEN_RESULT result)
{
    (void)context;
    (void)result;
}

TEST_F(TestSocketioOpenIot, opening_socketio_connection_succeeds_when_tcp_connects)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;
    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    this->on_io_open_complete = opening_io;

    tcpsocketconnection_create_fake.return_val = (TCPSOCKETCONNECTION_HANDLE *)0xDEADBEEF;
    tcpsocketconnection_connect_fake.return_val = 0;

    EXPECT_EQ(0,
              socketio_open(this->socket_io,
                            this->on_io_open_complete,
                            this->on_io_open_complete_context,
                            this->on_bytes_received,
                            this->on_bytes_received_context,
                            this->on_io_error,
                            this->on_io_error_context));

    socketio_destroy(this->socket_io);
}

TEST_F(TestSocketioOpenIot, closing_socketio_fails_when_nullptr_passed_for_socket)
{
    ON_IO_CLOSE_COMPLETE on_io_close_complete = {0};
    void *callback_context = {0};

    EXPECT_EQ(MU_FAILURE, socketio_close(nullptr, on_io_close_complete, callback_context));
}

void closing_io(void *context)
{
    (void)context;
}

TEST_F(TestSocketioOpenIot, closing_socketio_succeeds_when_socket_is_opened)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;
    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    tcpsocketconnection_create_fake.return_val = (TCPSOCKETCONNECTION_HANDLE *)0xDEADBEEF;
    tcpsocketconnection_connect_fake.return_val = 0;
    EXPECT_EQ(0,
              socketio_open(this->socket_io,
                            this->on_io_open_complete,
                            this->on_io_open_complete_context,
                            this->on_bytes_received,
                            this->on_bytes_received_context,
                            this->on_io_error,
                            this->on_io_error_context));

    ON_IO_CLOSE_COMPLETE on_io_close_complete = closing_io;
    void *callback_context = {0};
    EXPECT_EQ(0, socketio_close(this->socket_io, on_io_close_complete, callback_context));

    socketio_destroy(this->socket_io);
}

TEST_F(TestSocketioOpenIot, sending_over_socketio_fails_if_socketio_does_not_exist)
{
    void *buffer = (void *)0xBEADBEEF;
    size_t size = sizeof(buffer);
    ON_SEND_COMPLETE on_send_complete = {0};
    void *callback_context = {0};

    EXPECT_EQ(MU_FAILURE, socketio_send(nullptr, buffer, size, on_send_complete, callback_context));
}

TEST_F(TestSocketioOpenIot, sending_over_socketio_fails_if_no_buffer_exists)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;
    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    size_t size = 4 * sizeof(int);
    ON_SEND_COMPLETE on_send_complete = {0};
    void *callback_context = {0};

    EXPECT_EQ(MU_FAILURE, socketio_send(this->socket_io, nullptr, size, on_send_complete, callback_context));

    socketio_destroy(this->socket_io);
}

TEST_F(TestSocketioOpenIot, sending_over_socketio_fails_if_size_is_0)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;
    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    void *buffer = (void *)0xBEADBEEF;
    ON_SEND_COMPLETE on_send_complete = {0};
    void *callback_context = {0};

    EXPECT_EQ(MU_FAILURE, socketio_send(this->socket_io, buffer, 0, on_send_complete, callback_context));

    socketio_destroy(this->socket_io);
}

TEST_F(TestSocketioOpenIot, sending_over_socketio_fails_if_socketio_is_not_open)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;
    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    void *buffer = (void *)0xBEADBEEF;
    size_t size = sizeof(buffer);
    ON_SEND_COMPLETE on_send_complete = {0};
    void *callback_context = {0};

    EXPECT_EQ(MU_FAILURE, socketio_send(this->socket_io, buffer, size, on_send_complete, callback_context));

    socketio_destroy(this->socket_io);
}

TEST_F(TestSocketioOpenIot, sending_over_socketio_fails_if_adding_pending_io_fails)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;
    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    tcpsocketconnection_create_fake.return_val = (TCPSOCKETCONNECTION_HANDLE *)0xDEADBEEF;
    tcpsocketconnection_connect_fake.return_val = 0;
    EXPECT_EQ(0,
              socketio_open(this->socket_io,
                            this->on_io_open_complete,
                            this->on_io_open_complete_context,
                            this->on_bytes_received,
                            this->on_bytes_received_context,
                            this->on_io_error,
                            this->on_io_error_context));

    int buffer[] = {3, 7, 5, 4};
    size_t size = sizeof(buffer);
    ON_SEND_COMPLETE on_send_complete = {0};
    void *callback_context = {0};

    singlylinkedlist_add_fake.return_val = nullptr;

    EXPECT_EQ(MU_FAILURE, socketio_send(this->socket_io, (void *)buffer, size, on_send_complete, callback_context));

    socketio_destroy(this->socket_io);
}

void send_complete(void *, IO_SEND_RESULT)
{
    return;
}

TEST_F(TestSocketioOpenIot, sending_over_socketio_succeeds_when_pending_io_added)
{
    singlylinkedlist_create_fake.return_val = (SINGLYLINKEDLIST_HANDLE)0xDEADBEEF;
    this->socket_io = socketio_create((void *)&this->io_create_parameters);

    tcpsocketconnection_create_fake.return_val = (TCPSOCKETCONNECTION_HANDLE *)0xDEADBEEF;
    tcpsocketconnection_connect_fake.return_val = 0;
    EXPECT_EQ(0,
              socketio_open(this->socket_io,
                            this->on_io_open_complete,
                            this->on_io_open_complete_context,
                            this->on_bytes_received,
                            this->on_bytes_received_context,
                            this->on_io_error,
                            this->on_io_error_context));

    int buffer[] = {3, 7, 5, 4};
    size_t size = sizeof(buffer);
    ON_SEND_COMPLETE on_send_complete = send_complete;
    void *callback_context = {0};

    singlylinkedlist_add_fake.return_val = 0;

    EXPECT_EQ(MU_FAILURE, socketio_send(this->socket_io, (void *)buffer, size, on_send_complete, callback_context));

    socketio_destroy(this->socket_io);
}

TEST_F(TestSocketioOpenIot, set_option_returns_ok_regardless_of_input)
{
    EXPECT_EQ(OPTIONHANDLER_OK, socketio_setoption(nullptr, nullptr, nullptr));
}
